// Copyright 2007-2010 Comdiv (F. Sadykov) - http://code.google.com/u/fagim.sadykov/
// Supported by Media Technology LTD 
//  
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 
// MODIFICATIONS HAVE BEEN MADE TO THIS FILE
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Xml;
using System.Xml.Linq;
using Comdiv.Application;
using Comdiv.Collections;
using Comdiv.Common;
using Comdiv.Extensions;
using Comdiv.Inversion;
using Comdiv.Logging;
using System.Xml.XPath;

namespace Comdiv.Security{
    public class DefaultRoleResolver : IRoleResolver, IExtendedRoleResolver,IWithContainer{
        public ILog log = logger.get("comdiv.security.roleresolver");
        private IPrincipalSource _principalSource;
        public object lockSync = new object();

        private IInversionContainer _container;
        public IInversionContainer Container
        {
            get
            {
                if (_container.invalid())
                {
                    lock (this)
                    {
                        if (_container.invalid())
                        {
                            Container = myapp.ioc;
                        }
                    }
                }
                return _container;
            }
            set { _container = value; }
        }

        protected IDictionary<string, bool> resolveRoleCache = new Dictionary<string, bool>();

        public DefaultRoleResolver(){
            myapp.OnReload += applicationCenter_OnReload;
            this.Extensions = Container.all<IRoleResolverExtension>().ToList();
            
            Reload();
        }

        public List<IRoleResolverExtension> Extensions { get;protected set; }

        public bool IsInRole(IPrincipal principal, string role, bool useCache){
            lock (this){
                if (log.IsDebugEnabled){
                    log.Debug("start check role " + role + " for " + principal.Identity.Name);
                }
                bool result = false;
                try{
                    result = _isinrole(role, principal, useCache);
                }
                catch (Exception ex){
                    log.Error("error while role resolving", ex);
                    throw;
                }
                if (log.IsDebugEnabled){
                    log.Debug("stop check role " + role + " for " + principal.Identity.Name + " result - " + result);
                }
                return result;
            }
        }

        private bool _isinrole(string role, IPrincipal principal, bool useCache) {
            role = role.ToUpper();
            if ("DEFAULT" == role){
                return true;
            }
            if (role != "ADMIN" && role != "ROOT"){
                if (this.IsAdmin(principal)){
                    return false;
                }
            }
            var key = principal.Identity.Name + "_" + role;

            if (useCache){
                var cached = getFromCache(key);
                if (cached.HasValue){
                    return cached.Value;
                }
            }


            var result = internalIsInRole(principal, role);
            

            storeToCache(key, result);
           
            return result;
        }

        public IEnumerable<string> GetRoles(IPrincipal principal){
            lock (this){
                return internalGetRoles(principal).ToArray();
            }
        }

        private IEnumerable<string> internalGetRoles(IPrincipal principal)
        {
            foreach (var role in internalGetActive(principal)){
                if (role == "DEFAULT"){
                    continue;
                }

                yield return role;
            }
            //always return at end
            yield return "DEFAULT";
        }

        public void AssignToRole(string user, string role, bool persistable){
            lock (this){


                resolveRoleCache[user + "_" + role] = true;
                if (persistable){
                    persistAssignToRole(user, role);
                }
            }
        }

      


        private void applicationCenter_OnReload(object sender, EventWithDataArgs<int> args){
            lock (this){
                Reload();
            }
        }

        
        public List<string> ApplicationRoles { get; set; }


        public void Reload(){
            lock (this){
                resolveRoleCache.Clear();
                this.SetupDefaults();
                internalReload();
            }
        }

        public bool IsInRoleExact(string role)
        {
            return IsInRoleExact(myapp.usr, role);
        }

        public bool IsInRoleExact(IPrincipal principal, string role)
        {
            if (UserMap[principal.Identity.Name].Contains(role.ToUpper()))
            {
                return true;
            }
            return false;
        }

        protected virtual IEnumerable<string> internalGetActive(IPrincipal principal){
            foreach (var role in ApplicationRoles)
            {
                if (IsInRole(principal, role, true))
                {
                    yield return role;
                }
            }
        }
       

        protected virtual void storeToCache(string key, bool result){
            resolveRoleCache[key] = result;
        }

        protected virtual bool? getFromCache(string key){
            if (resolveRoleCache.ContainsKey(key)){
                return resolveRoleCache[key];
            }
            return null;
        }

        private bool usewindows = true;

        protected virtual bool internalIsInRole(IPrincipal principal, string role){
            bool dolog = log.IsDebugEnabled;
            if(dolog)log.Debug("try by principal self method");
            
            try{
                if (usewindows || !(principal is WindowsPrincipal)){
                    if (principal.IsInRole(role)) return true;
                }
            }catch(SystemException){
                if(principal is WindowsPrincipal){
                    usewindows = false;
                }else{
                    throw;
                }
            }
            if (dolog) log.Debug("try resolve by usermap with name");
            if (UserMap[principal.Identity.Name].Contains(role.ToUpper())){return true;}
            if (principal.Identity.Name.Contains("\\"))
            {
                if (dolog) log.Debug("try resolve by usermap by domain name");
                var domain = principal.Identity.Name.Split('\\')[0];
                if (UserMap[domain].Contains(role.ToUpper()))
                {
                    return true;
                }
            }
            if (dolog) log.Debug("try resolve by rolemap");
            foreach (var outerRole in RoleMap.Reverse(role))
            {
                //reqursion
                if (IsInRole(principal, outerRole,true))
                {
                    return true;
                }
            }
            if (dolog) log.Debug("try resolve by usermap with extensions");
            foreach (var extension in Extensions){
                if(extension.IsInRole(principal,role)){
                    return true;
                }
            }
            if (dolog) log.Debug("role was not recognized for user for all methods");
            return false;
        }

        protected virtual void internalReload(){
            
        }


        protected virtual void persistAssignToRole(string user, string role){
            if (SourceFile.noContent()) return;
            var doc = XElement.Load(SourceFile);
            doc.Add(
                new XElement("map",new XAttribute("user",user),new XAttribute("as",role))
                );
            doc.Save(SourceFile);

        }

        public string SourceFile { get; set; }

        public StringMap RoleMap { get; set; }

        public StringMap UserMap { get; set; }
    }
}